/*
 * Copyright 2010, 2011 by the University of Richmond, Richmond, VA 23173
 * Written by Eric F. Palmer eric@ericfpalmer.com || epalmer@richmond.edu
 * And other contributors
 * http://www.richmond.edu/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Project files reside at http://code.google.com/p/files-2-exist-db
 */

/*
 * Exist DB Rest http class and methods
 *
 * not all http rest method are implemented yet
 *
 * NOTE: this is an eXist-Db REST Wrapper class
 * It is intended to be used in well behaved eXist-Db environments
 *
 * For example it might be called by a program that scans directories for files that need to be pushed (put) to eXist-Db
 *
 * Response headers are not directly available but could be made available if we wanted to add our own responseHeader
 *
 * However the exception stack trace and message can be used to denote exception causes
 *
 * THIS CLASS closes the http connection after every use. 
 * This is the safest approach but not the best for performance.
 *
 * Intended uses don't require any better performance
 *
 *
 *
 * @version 1.0
 * @author epalmer
 */


/* WARNING
 * if you turn log4j debug logging on then the username and password credentials will be written to the transferlog and main log file.
 */



package edu.richmond.is.webservices.files2exist;

import java.net.URI;
import java.net.URISyntaxException;

import edu.richmond.is.webservices.util.Timer;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;

import java.io.File;
import java.io.IOException;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.Credentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.entity.FileEntity;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.apache.log4j.Logger;


public class ExistRest {

    private String    hostname;
    private int       port;
    private boolean   ssl;
    private URI       lastUri;  // only for retreiving the uri if needed to validate the url
    private long      lastResponseBodyLength              = 0;
    private long      lastEntityUploadLength              = 0;
    private double    lastDownloadSpeedCharPerSecond      = 0.0;
    private double    lastUploadSpeedCharPerSecond        = 0.0;
    private long      lastUploadEtMilliSeconds            = 0;
    private long      lastDownloadEtMilliSeconds          = 0;
    private Logger    transferLogger                      = Logger.getLogger("transferlog");
    
    public ExistRest(String hostname, int port, boolean ssl)
    {
        this.hostname = hostname;
        this.port     = port;
        this.ssl      = ssl;
    }

    private void clearLastProperties()
    {
        this.lastResponseBodyLength              = 0;
        this.lastEntityUploadLength              = 0;
        this.lastDownloadSpeedCharPerSecond      = 0.0;
        this.lastUploadSpeedCharPerSecond        = 0.0;
        this.lastUploadEtMilliSeconds            = 0;
        this.lastDownloadEtMilliSeconds          = 0;
    }
    public long getLastEntityUploadLength (){
        return this.lastEntityUploadLength;
    }
    public long getLastResponseBodyLength()
    {
        return this.lastResponseBodyLength;
    }
    public long getLastDownloadEtMilliSeconds() {
        return this.lastDownloadEtMilliSeconds;
    }
    public long getLastUploadEtMilliSeconds() {
        return this.lastUploadEtMilliSeconds;
    }
    private void setLastDownloadSpeedCharPerSecond() {
        double bytesPerSecond = this.lastResponseBodyLength/(this.lastDownloadEtMilliSeconds/1000.0);
        this.lastDownloadSpeedCharPerSecond = bytesPerSecond;
    }
    public long getLastDownloadSpeedCharPerSecond()
    {
        return (long)(this.lastDownloadSpeedCharPerSecond);
    }
    public long getLastUploadSpeedCharPerSecond()
    {
        return (long)(this.lastUploadSpeedCharPerSecond);
    }
    public void setLastUploadSpeedCharPerSecond() {
        double bytesPerSecond = this.lastEntityUploadLength/(this.lastUploadEtMilliSeconds/1000.0);
        this.lastUploadSpeedCharPerSecond = bytesPerSecond;
    }

    public URI getLastUri() {
        return this.lastUri;
    }

    // if credentials not needed pass null for username and password
    public String restGet(String path, String username, String password) throws Exception
    {
        this.clearLastProperties();

        Timer sw = new Timer();
        sw.start();

        DefaultHttpClient httpclient = new DefaultHttpClient();
        String protocol = "http";
        if (this.ssl) {
            protocol = "https";
        }

        URI uri = URIUtils.createURI(protocol, this.hostname, this.port, path,
                null , null);
        
        this.lastUri = uri;

        HttpGet httpget = new HttpGet(uri);
        //System.out.println(httpget.getURI());
        if (username == null || password == null) {

        } else {
            httpclient.getCredentialsProvider().setCredentials(
                new AuthScope(this.hostname, 8080, AuthScope.ANY_REALM, "basic"),
                new UsernamePasswordCredentials(username, password));
        }
        
        ResponseHandler<String> responseHandler = new BasicResponseHandler();
        String responseBody = httpclient.execute(httpget, responseHandler);
        //System.out.println("inside responseBody: "+responseBody );
        httpclient.getConnectionManager().shutdown();

        sw.stop();
        //System.out.println("=====" + this.lastDownloadEtMilliSeconds);
        this.lastResponseBodyLength = responseBody.length();
        this.lastDownloadEtMilliSeconds = sw.getET();
        this.setLastDownloadSpeedCharPerSecond();

        // TODO how do we clean these up if an exception is called
        // TODO if the calling class is st to null do all the objects in the class get cleaned up?
        httpclient = null;
        uri        = null;
        httpget    = null;

        return responseBody;
    }


    // pass complete path and filename
    // TODO refactor name, does not meet naming convention
    public void restPut(File filename, String contentType, String path, String username, String password) throws IOException, URISyntaxException
    {
        if (contentType == null){
            contentType = "text/xml";
        }
        this.clearLastProperties();

        Timer sw = new Timer();
        sw.start();

        // the following block is right out of the apache httpdClient manual and is necessary for premeptive basic auth
        HttpRequestInterceptor preemptiveAuth = new HttpRequestInterceptor() {
            public void process(
                    final HttpRequest request,
                    final HttpContext context) throws HttpException, IOException {

                 AuthState authState = (AuthState) context.getAttribute(
                        ClientContext.TARGET_AUTH_STATE);

                 CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(
                        ClientContext.CREDS_PROVIDER);

                 HttpHost targetHost = (HttpHost) context.getAttribute(
                        ExecutionContext.HTTP_TARGET_HOST);

                 // If not auth scheme has been initialized yet
                 if (authState.getAuthScheme() == null) {
                    AuthScope authScope = new AuthScope(
                            targetHost.getHostName(),
                            targetHost.getPort());
                    // Obtain credentials matching the target host

                    Credentials creds = credsProvider.getCredentials(authScope);
                    // If found, generate BasicScheme preemptively
                    if (creds != null) {
                        authState.setAuthScheme(new BasicScheme());
                        authState.setCredentials(creds);
                    }
               }
           }
        };

        
        DefaultHttpClient httpclient = new DefaultHttpClient();

        // the following turns on premeptive basic auth
        httpclient.addRequestInterceptor(preemptiveAuth, 0);

        String protocol = "http";
        if (this.ssl) {
            protocol = "https";
        }

        URI uri = new URI(protocol, null, this.hostname, this.port, path,
                null , null);

        this.lastUri = uri;

        HttpPut httpput = new HttpPut(uri);
        FileEntity myFile = new FileEntity(filename, contentType);
        httpput.setEntity(myFile);
        //System.out.println(httpget.getURI());
        if (username == null || password == null) {
            transferLogger.warn("********NULL Credentials Detected: " + username + "/" + password + " **************");
        } else {
            transferLogger.debug("********Setting Credentials " + username + "/" + password + " **************");
            //httpclient.getParams().setAuthenticationPreemptive(true);

            httpclient.getCredentialsProvider().setCredentials(
                new AuthScope(this.hostname, 8080, AuthScope.ANY_REALM, "basic"),
                new UsernamePasswordCredentials(username, password));
        }
        //TODO put try catch here and log some info about what is going on with the file and the put
        ResponseHandler<String> responseHandler = new BasicResponseHandler();
        String responseBody = httpclient.execute(httpput, responseHandler);
        //System.out.println("inside responseBody: "+responseBody );
        httpclient.getConnectionManager().shutdown();

        sw.stop();
        //System.out.println("=====" + this.lastDownloadEtMilliSeconds);
        long sizeOfResponseBody = responseBody.length();
        this.lastEntityUploadLength = filename.length();
        this.lastUploadEtMilliSeconds = sw.getET();
        this.setLastUploadSpeedCharPerSecond();

        // TODO how do we clean these up if an exception is called
        // TODO if the calling class is st to null do all the objects in the class get cleaned up?
        httpclient = null;
        uri        = null;
        httpput    = null;
    }


}


